Micron Document
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
| SparkN0de-git | SparkN0de |
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


Commit af56178064f032b58cfbe97b556131877da93ff8


Parents : 7f891d6
Author : Mark Qvist <mark@unsigned.io>
Date : 2024-03-17T00:23:55+01:00

Added image and file attachment saving

Changes

2 files changed, 173 insertions(+), 2 deletions(-)

M sbapp/main.py +7 -2

Diff

diff --git a/sbapp/main.py b/sbapp/main.py
index f2a109cd..cf6ed910 100644
--- a/sbapp/main.py
+++ b/sbapp/main.py
@@ -470,7 +470,12 @@ class SidebandApp(MDApp):
File = autoclass("java.io.File")
FileProvider = autoclass("androidx.core.content.FileProvider")
- image.save(file_path)
+ if isinstance(image, bytes):
+ with open(file_path, "wb") as export_file:
+ export_file.write(image)
+ else:
+ image.save(file_path)
+
i_file = File(file_path)
image_uri = FileProvider.getUriForFile(mActivity, "io.unsigned.sideband.provider", i_file)
@@ -484,7 +489,7 @@ class SidebandApp(MDApp):
ok_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
dialog = MDDialog(
title="Export Error",
- text="The QR-code could not be exported and shared:\n\n"+str(e),
+ text="The resource could not be exported and shared:\n\n"+str(e),
buttons=[ ok_button ],
)
def dl_ok(s):

diff --git a/sbapp/ui/messages.py b/sbapp/ui/messages.py
index 7f054978..97160c94 100644
--- a/sbapp/ui/messages.py
+++ b/sbapp/ui/messages.py
@@ -202,6 +202,9 @@ class Messages():
extra_telemetry = {}
telemeter = None
image_field = None
+ has_image = False
+ attachments_field = None
+ has_attachment = False
force_markup = False
signature_valid = False
@@ -239,9 +242,18 @@ class Messages():
if "lxm" in m and m["lxm"] and m["lxm"].fields != None and LXMF.FIELD_IMAGE in m["lxm"].fields:
try:
image_field = m["lxm"].fields[LXMF.FIELD_IMAGE]
+ has_image = True
except Exception as e:
pass
+ if "lxm" in m and m["lxm"] and m["lxm"].fields != None and LXMF.FIELD_FILE_ATTACHMENTS in m["lxm"].fields:
+ if len(m["lxm"].fields[LXMF.FIELD_FILE_ATTACHMENTS]) > 0:
+ try:
+ attachments_field = m["lxm"].fields[LXMF.FIELD_FILE_ATTACHMENTS]
+ has_attachment = True
+ except Exception as e:
+ pass
+
rcvd_d_str = ""
trcvd = telemeter.read("received") if telemeter else None
@@ -331,14 +343,24 @@ class Messages():
pre_content += "[b]Warning![/b] The signature for this message could not be validated. [b]This message is likely to be fake[/b].\n\n"
force_markup = True
+ if has_attachment:
+ heading_str += "\n[b]Attachments[/b] "
+ for attachment in attachments_field:
+ heading_str += str(attachment[0])+", "
+ heading_str = heading_str[:-2]
+
item = ListLXMessageCard(
text=pre_content+message_markup.decode("utf-8")+extra_content,
heading=heading_str,
md_bg_color=msg_color,
)
+ if has_attachment:
+ item.attachments_field = attachments_field
+
if image_field != None:
item.has_image = True
+ item.image_field = image_field
img = item.ids.message_image
img.source = ""
img.texture = CoreImage(io.BytesIO(image_field[1]), ext=image_field[0]).texture
@@ -425,6 +447,134 @@ class Messages():
return x
+ def gen_save_image(item):
+ if RNS.vendor.platformutils.is_android():
+ def x():
+ image_field = item.image_field
+ extension = str(image_field[0]).replace(".", "")
+ filename = time.strftime("LXM_%Y_%m_%d_%H_%M_%S", time.localtime(time.time()))+"."+str(extension)
+
+ self.app.share_image(image_field[1], filename)
+ item.dmenu.dismiss()
+ return x
+
+ else:
+ def x():
+ image_field = item.image_field
+ try:
+ extension = str(image_field[0]).replace(".", "")
+ filename = time.strftime("LXM_%Y_%m_%d_%H_%M_%S", time.localtime(time.time()))+"."+str(extension)
+ if RNS.vendor.platformutils.is_darwin():
+ save_path = str(plyer.storagepath.get_downloads_dir()+filename).replace("file://", "")
+ else:
+ save_path = plyer.storagepath.get_downloads_dir()+"/"+filename
+
+ with open(save_path, "wb") as save_file:
+ save_file.write(image_field[1])
+
+ item.dmenu.dismiss()
+
+ ok_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
+ dialog = MDDialog(
+ title="Image Saved",
+ text="The image has been saved to: "+save_path+"",
+ buttons=[ ok_button ],
+ # elevation=0,
+ )
+ def dl_ok(s):
+ dialog.dismiss()
+
+ ok_button.bind(on_release=dl_ok)
+ dialog.open()
+
+ except Exception as e:
+ item.dmenu.dismiss()
+ ok_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
+ dialog = MDDialog(
+ title="Error",
+ text="Could not save the image:\n\n"+save_path+"\n\n"+str(e),
+ buttons=[ ok_button ],
+ # elevation=0,
+ )
+ def dl_ok(s):
+ dialog.dismiss()
+
+ ok_button.bind(on_release=dl_ok)
+ dialog.open()
+
+ item.dmenu.dismiss()
+
+ return x
+
+ def gen_save_attachment(item):
+ def x():
+ attachments_field = item.attachments_field
+ if isinstance(attachments_field, list):
+ try:
+ if RNS.vendor.platformutils.is_darwin():
+ output_path = str(plyer.storagepath.get_downloads_dir()).replace("file://", "")
+ else:
+ output_path = plyer.storagepath.get_downloads_dir()+"/"
+
+ if len(attachments_field) == 1:
+ saved_text = "The attached file has been saved to: "+output_path
+ saved_title = "Attachment Saved"
+ else:
+ saved_text = "The attached files have been saved to: "+output_path
+ saved_title = "Attachment Saved"
+
+ for attachment in attachments_field:
+ filename = str(attachment[0]).replace("../", "").replace("..\\", "")
+ if RNS.vendor.platformutils.is_darwin():
+ save_path = str(plyer.storagepath.get_downloads_dir()+filename).replace("file://", "")
+ else:
+ save_path = plyer.storagepath.get_downloads_dir()+"/"+filename
+
+ name_counter = 1
+ pre_count = save_path
+ while os.path.exists(save_path):
+ save_path = str(pre_count)+"."+str(name_counter)
+ name_counter += 1
+
+ saved_text = "The attached file has been saved to: "+save_path
+
+ with open(save_path, "wb") as save_file:
+ save_file.write(attachment[1])
+
+ item.dmenu.dismiss()
+
+ ok_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
+ dialog = MDDialog(
+ title=saved_title,
+ text=saved_text,
+ buttons=[ ok_button ],
+ # elevation=0,
+ )
+ def dl_ok(s):
+ dialog.dismiss()
+
+ ok_button.bind(on_release=dl_ok)
+ dialog.open()
+
+ except Exception as e:
+ item.dmenu.dismiss()
+ ok_button = MDRectangleFlatButton(text="OK",font_size=dp(18))
+ dialog = MDDialog(
+ title="Error",
+ text="Could not save the attachment:\n\n"+save_path+"\n\n"+str(e),
+ buttons=[ ok_button ],
+ # elevation=0,
+ )
+ def dl_ok(s):
+ dialog.dismiss()
+
+ ok_button.bind(on_release=dl_ok)
+ dialog.open()
+
+ item.dmenu.dismiss()
+
+ return x
+
def gen_copy_telemetry(telemeter, extra_telemetry, item):
def x():
try:
@@ -670,6 +820,22 @@ class Messages():
"on_release": gen_del(m["hash"], item)
}
]
+ if has_image:
+ extra_item = {
+ "viewclass": "OneLineListItem",
+ "text": "Save image",
+ "height": dp(40),
+ "on_release": gen_save_image(item)
+ }
+ dm_items.append(extra_item)
+ if has_attachment:
+ extra_item = {
+ "viewclass": "OneLineListItem",
+ "text": "Save attachment",
+ "height": dp(40),
+ "on_release": gen_save_attachment(item)
+ }
+ dm_items.append(extra_item)
item.dmenu = MDDropdownMenu(
caller=item.ids.msg_submenu,


──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────